package com.liz.lizrpccore.core;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.liz.lizrpcapi.commons.Node;
import com.liz.lizrpccore.annotations.CustomerResource;
import com.liz.lizrpccore.annotations.ProviderService;
import com.liz.lizrpccore.beans.ResourceBean;
import com.liz.lizrpccore.core.consts.GlobalConst;
import com.liz.lizrpccore.core.providers.LizPrcProvider;
import com.liz.lizrpccore.core.providers.redis.LizRpcRedisProvider;
import com.liz.lizrpccore.entity.UniqueClientId;
import com.liz.lizrpccore.enums.ProxyTypeEnum;
import com.liz.lizrpccore.properties.LizRpcProperties;
import com.liz.lizrpccore.templates.ApplicationContextTemplate;
import com.liz.lizrpccore.utils.LizStringUtil;
import com.liz.lizrpccore.utils.UniqueIpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author liangGongWei
 * @description
 * @create 2021-08-05 16:34
 **/

@Slf4j
@Configuration
@EnableConfigurationProperties(LizRpcProperties.class)
@ConditionalOnProperty(prefix = "liz.rpc", name = "host")
public class LizRpcConfig {


    @Autowired
    private LizRpcProperties lizRpcProperties;

    @Autowired
    private ApplicationContextTemplate applicationContextTemplate;


    /**
     * 存放本地类名
     */
    public List<String> classNameList = new ArrayList<>();
    /**
     * 存放本地类的方法
     */
    Map<String, List<String>> classMethodNameMap = new HashMap<>();

    /**
     * 存放远程类
     */
    List<String> remoteClassNameList = new ArrayList<>();

    /**
     * 保存class节点
     * key:     className
     * value:   map:
     *                  key: clientId
     *                  value:Node
     */
    public static Map<String, Map<String,Node>> classNodeMap = new ConcurrentHashMap<>();


    @Bean
    public LizRpcProperties lizRpcProperties(LizRpcProperties lizRpcProperties) {
        GlobalConst.setProxyType(lizRpcProperties.getProxyType());
        // 扫描包下所有class
        this.doScanPackage(this.lizRpcProperties.getBasePackage());
        //找出有providerService注解的class
        this.getAnnotationProviderServiceClass();
        // 注册(发送到rpc-server)
        this.sendToRpcServer(lizRpcProperties);
        //在rpc-server中查找注册的类
        this.getRemoteClassFromRpcServer();
        // 将有 LizPrcProvider 注册到spring上下文
        this.LizPrcProviderSaveContext();
        // 监听远程调用
        this.listenRemoteCall();
        return lizRpcProperties;
    }

    private void listenRemoteCall(){
        LizPrcProvider provider = ProxyTypeEnum.getProvider(lizRpcProperties.getProxyType());
        LizPrcProvider bean = this.applicationContextTemplate.getApplicationContext().getBean(provider.getClass());
        bean.providerToRemote(classNameList,lizRpcProperties);
    }

    private void LizPrcProviderSaveContext() {
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) this.applicationContextTemplate.getApplicationContext();
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
        for (ProxyTypeEnum value : ProxyTypeEnum.values()) {
            try {
                LizPrcProvider lizPrcProvider = value.getLizPrcProvider();
                Class<? extends LizPrcProvider> clazz = lizPrcProvider.getClass();
                // 通过BeanDefinitionBuilder创建bean定义
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
                defaultListableBeanFactory.registerBeanDefinition(clazz.getName(), beanDefinitionBuilder.getRawBeanDefinition());
            }catch (Exception e){
                log.warn("注入provider失败，className:{}", value.getType());
            }
        }
    }

    private void sendToRpcServer(LizRpcProperties lizRpcProperties) {
        String url = this.lizRpcProperties.getHost() + ":" + this.lizRpcProperties.getPort() + UniqueClientId.REGISTER_MAPPING;
        JSONObject jsonObject = this.getRegisterJson(classMethodNameMap,lizRpcProperties);
        CloseableHttpClient client = null;
        CloseableHttpResponse response = null;
        try {
            client = HttpClients.createDefault();
            HttpPost post = new HttpPost(url);
            StringEntity stringEntity = new StringEntity(jsonObject.toJSONString());
            post.setEntity(stringEntity);
            post.setHeader("Content-Type", "application/json");
            response = client.execute(post);
            log.info(response.getStatusLine().getStatusCode() + "-----");

        } catch (Exception e) {
            log.warn("register to server error,msg:{}", e.getMessage());
        }finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (client != null) {
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private void getRemoteClassFromRpcServer() {
        String url = this.lizRpcProperties.getHost() + ":" + this.lizRpcProperties.getPort() + UniqueClientId.REMOTE_CLASS_QUERY;
        CloseableHttpClient client = null;
        CloseableHttpResponse response = null;
        String result = null;
        try {
            client = HttpClients.createDefault();
            URIBuilder uri = new URIBuilder(url);
            HttpGet httpGet = new HttpGet(uri.build());
            response = client.execute(httpGet);
            int status = response.getStatusLine().getStatusCode();
            if (status == HttpStatus.SC_OK) {
                HttpEntity httpEntity = response.getEntity();
                if (httpEntity != null) {
                    result = EntityUtils.toString(httpEntity, "UTF-8");
                    // 关闭资源
                    EntityUtils.consume(httpEntity);
                    Map<String, Map<String,Node>> resMap = JSON.parseObject(result, new TypeReference<Map<String, Map<String,Node>>>() {
                    });
                    Set<String> strings = resMap.keySet();
                    this.remoteClassNameList = new ArrayList<>(strings);
                    classNodeMap = resMap;
                }
            } else {
                log.warn("query from server error");
            }
        } catch (Exception e) {
            log.warn("query from server error,msg:{}", e.getMessage());
        }
        if (response != null) {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (client != null) {
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void getAnnotationProviderServiceClass() {
        if (CollectionUtils.isEmpty(this.classNameList)) {
            return;
        }
        Iterator<String> iterator = this.classNameList.iterator();
        while (iterator.hasNext()) {
            try {
                String className = iterator.next();
                Class clazz = Class.forName(className);
                if (!clazz.isAnnotationPresent(ProviderService.class)) {
                    iterator.remove();
                    continue;
                }
                String name = className;
                Class[] interfaces = clazz.getInterfaces();
                if (interfaces.length > 0) {
                    Class anInterface = interfaces[0];
                    name = anInterface.getName();
                }
                // 这里可以过滤掉Object类的方法，但是没有必要，先不搞
                // List<Method> methods = Arrays.asList(clazz.getMethods()).stream().filter(item -> !Object.class.equals(item.getDeclaringClass()))
                List<Method> methods = Arrays.asList(clazz.getMethods());
                List<String> collect = methods.stream().map(Method::getName).collect(Collectors.toList());
                this.classMethodNameMap.put(name, collect);
            } catch (Exception e) {
                log.warn("扫描class异常：{}", iterator.next());
            }
        }
    }

    private void doScanPackage(String basePackages) {
        String scanPath = basePackages.replaceAll("\\.", "/");
        //获取到当前包所在磁盘的全路径
        URL url = this.getClass().getClassLoader().getResource(scanPath);
        assert url != null;
        File files = new File(url.getFile());
        for (File file : files.listFiles()) {
            if (file.isDirectory()) {
                doScanPackage(basePackages + "." + file.getName());
            } else {
                this.classNameList.add(basePackages + "." + file.getName().replace(".class", ""));
            }
        }
    }

    private JSONObject getRegisterJson(Map<String, List<String>> classMethodNameMap, LizRpcProperties lizRpcProperties) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("clientId", UniqueClientId.CLIENT_ID);
        jsonObject.put("classMethodNameMap", classMethodNameMap);
        jsonObject.put("ip", UniqueIpUtils.getHost());
        jsonObject.put("protocolPort",lizRpcProperties.getProtocolPort());
        log.info(jsonObject.toJSONString());
        System.out.println();
        return jsonObject;
    }


}
